include!(concat!(env!("OUT_DIR"), "/dict.rs"));

pub fn converter(input: &str) -> String {
    let mut result = query(input);
    if let Some((capital, _)) = result.split_at_mut_checked(1) {
        capital.make_ascii_uppercase()
    }
    result
}

#[inline]
fn common_prefix<'a>(a: &'a str, b: &str) -> &'a str {
    let len = a
        .chars()
        .zip(b.chars())
        .take_while(|(a, b)| a == b)
        .map(|(a, _)| a.len_utf8())
        .sum();

    &a[..len]
}

struct QueryOnce<'a> {
    result: Option<&'static str>,
    remaining: &'a str,
}

fn query_once<'a>(input: &'a str, mut dict: &[(&'static str, &'static str)]) -> QueryOnce<'a> {
    let result = {
        let mut input = input;

        loop {
            match dict.binary_search_by_key(&input, |item| item.0) {
                Ok(i) => break Some(DICT[i]),
                Err(i) => {
                    let &(word, conv) = dict.get(i.saturating_sub(1)).unwrap();
                    let prefix = common_prefix(input, word);
                    match prefix {
                        "" => break None,
                        p if p == word => break Some((word, conv)),
                        p => {
                            input = p;
                            // The prefix must appear before the current word in the dictionary.
                            dict = &dict[..i];
                        }
                    }
                }
            };
        }
    };

    if let Some((word, conv)) = result {
        QueryOnce {
            result: Some(conv),
            remaining: &input[word.len()..],
        }
    } else {
        QueryOnce {
            result: None,
            remaining: input,
        }
    }
}

fn query(mut input: &str) -> String {
    let mut fragments: Vec<&str> = Vec::new();
    loop {
        // Skip non-ideograph `char`s since they do not have pronunciation
        input = input.trim_start_matches(|c: char| !is_cjk_ideograph(c));

        if input.is_empty() {
            return fragments.join(" ");
        }

        let QueryOnce { result, remaining } = query_once(input, DICT);

        match result {
            Some(pron) => {
                fragments.push(pron);
                input = remaining;
            }
            None => {
                // We cannot find a pronunciation for the current ideograph
                panic!(
                    "Cannot find a pronunciation for the ideograph: {}",
                    input.chars().next().unwrap()
                );
            }
        }
    }
}

fn is_cjk_ideograph(c: char) -> bool {
    match c as u32 {
        // Although these ranges are wide,
        // only a small portion is contained in the dictionary we use
        0x3400..=0x4DBF | // CJK Unified Ideographs Extension A
        0x4E00..=0x9FFF | // CJK Unified Ideographs
        0xF900..=0xFAFF | // CJK Compatibility Ideographs
        0x20000..=0x3FFFF // Supplementary Ideographic Plane and Tertiary Ideographic Plane
        => true,
        _ => false,
    }
}
